package com.chenwc.netty.tcp.config;

import io.netty.channel.Channel;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 定义一个ConcurrentHashMap,用来保存当前的连接。
 *
 * @author chenwc
 * @date 2023/5/28 9:28
 */
public class ChannelMap {

    private final static Logger log = LoggerFactory.getLogger(ChannelMap.class);

    //连接数
    public static int channelNum = 0;
    //concurrentHashmap以解决多线程冲突
    private static ConcurrentHashMap<String, Channel> channelHashMap = null;
    // 锁对象
    private static final Lock lock = new ReentrantLock();
    //加锁超时时间
    private static final int LOCK_TIMEOUT = 30;

    public static ConcurrentHashMap<String, Channel> getChannelHashMap() {
        return channelHashMap;
    }

    /**
     * 通过key获取ConcurrentHashMap里面的Channel
     * @param name key
     * @return Channel
     */
    public static Channel getChannelByName(String name) {
        if (channelHashMap == null || channelHashMap.isEmpty()) {
            return null;
        }
        //键为空
        if (StringUtils.isEmpty(name)){
            return null;
        }
        return channelHashMap.get(name);
    }

    /**
     * 给ConcurrentHashMap添加新的Channel
     * @param name key
     * @param channel Channel
     */
    public static void addChannel(String name, Channel channel) {
        if (channelHashMap == null) {
            channelHashMap = new ConcurrentHashMap<>();
        }
        try {
            //ConcurrentHashMap本身是线程安全的，此处加锁的目的是锁住channelNum
            if (lock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS)) {
                try {
                    //不包含这个key的时候才去做新增
                    if (!channelHashMap.containsKey(name) && !isContainChannelId(channel.id().asLongText())) {
                        channelHashMap.put(name, channel);
                        channelNum = channelHashMap.size();
                        log.info("当前客户端连接数： " + channelNum);
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    lock.unlock();   //释放锁
                }
            }
        } catch (InterruptedException e) {
            log.error("当前线程: {} 在获取锁时等待时间超过 {} 秒被中断", Thread.currentThread().getName(), LOCK_TIMEOUT);
            e.printStackTrace();
        }
    }

    /**
     * 通过key移除ConcurrentHashMap里面的Channel
     * @param name key
     * @return 移除结果，移除成功返回true，其他返回false
     */
    public static boolean removeChannelByName(String name) {
        if (channelHashMap == null || channelHashMap.isEmpty()) {
            channelNum = 0;
            return false;
        }
        //键为空
        if (StringUtils.isEmpty(name)) {
            return false;
        }
        try {
            //ConcurrentHashMap本身是线程安全的，此处加锁的目的是锁住channelNum
            if (lock.tryLock(LOCK_TIMEOUT, TimeUnit.SECONDS)) {
                boolean result = false;
                try {
                    if (channelHashMap.containsKey(name)) {
                        channelHashMap.remove(name);
                        channelNum = channelHashMap.size();
                        log.info("当前客户端连接数： " + channelNum);
                        result = true;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    lock.unlock();   //释放锁
                }
                return result;
            } else {
                return false;
            }
        } catch (InterruptedException e) {
            log.error("当前线程: {} 在获取锁时等待时间超过 {} 秒被中断", Thread.currentThread().getName(), LOCK_TIMEOUT);
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 判断channelHashMap是否已有channelId，避免同一个channel使用不同ep_id
     * @param channelId channelId
     * @return channelHashMap是否已有channelId
     */
    private static boolean isContainChannelId(String channelId){
        for (String key : channelHashMap.keySet()) {
            if (Objects.equals(channelId, channelHashMap.get(key).id().asLongText())){
                return true;
            }
        }
        return false;
    }
}
